﻿using System;
using System.Collections.Generic;
using System.Text;
using Vortex.Wrapper.Direct3D;
using Vortex.Wrapper;
using System.Runtime.InteropServices;
using Vortex.Debugging;

namespace Vortex.Drawing {

	/// <summary>
	/// Generic delegate callback for custom vertex converter code
	/// </summary>
	public delegate Vertex2D[] VertexConverter<T> (T obj);

	/// <summary>
	/// Vertex buffer for rarely changed static data
	/// </summary>
	public class VertexBuffer : Resource {
		///<summary>Internal vertex buffer object</summary>
		private Direct3DVertexBuffer9 _vertexBuffer;

		///<summary>Type of data in this vertex buffer</summary>
		private PrimitiveType _primitiveType;
		///<summary>Current number of vertices in buffer</summary>
		private int _vertexCount;

		///<summary>List of vertices in memory buffer</summary>
		private List<Vertex2D> _vertices = new List<Vertex2D>();

		/// <summary>
		/// Initializes a new instance of the <see cref="VertexBuffer"/> class.
		/// </summary>
		/// <param name="primitiveType">Type of the primitives in buffer.</param>
		public VertexBuffer(PrimitiveType primitiveType) {
			_primitiveType = primitiveType;
		}

		/// <summary>
		/// Create internal buffer for specified number of vertices
		/// </summary>
		/// <param name="vertexCount">The vertex count.</param>
		private void CreateBuffer(int vertexCount) {
			HResult hr = Device.ApiRef.CreateVertexBuffer((uint)(vertexCount * Marshal.SizeOf(typeof(Vertex2D))), D3DUsage.WriteOnly, D3DFVF.None, D3DPool.Default, out _vertexBuffer);
			Watcher.Assert(hr, "Create static vertex buffer");
			
			_vertexCount = vertexCount;
		}

		/// <summary>
		/// Drops buffer and reset all data counts
		/// </summary>
		private void DropBuffer() {
			if (null != _vertexBuffer) {
				_vertexBuffer.Dispose();
				_vertexBuffer = null;
				_vertexCount = 0;
			}
		}


		/// <summary>
		/// Calculate number of primitives in buffer
		/// </summary>
		/// <returns>Number of primitives in buffer</returns>
		private int GetPrimitiveCount() {
			return BufferedVertexStream.CalculatePrimitiveCount(_primitiveType, _vertexCount);
		}

		#region Resource Methods Overrides

		///<summary>Frees all system resource data associated with this instance of device resource</summary>
		protected override void  FreeData() {
			DropBuffer();
		}

		/// <summary>
		/// Invoked before the device is reset.
		/// </summary>
		protected override void BeforeDeviceReset() {
			DropBuffer();
		}

		/// <summary>
		/// Invoked after the device is reset successfully.
		/// </summary>
		protected override void AfterDeviceReset() {
			Update();
		}

		#endregion

		internal Direct3DVertexBuffer9 ApiRef {
			get { return _vertexBuffer; }
		}

		/// <summary>
		/// Gets the type of the primitive.
		/// </summary>
		/// <value>The type of the primitive.</value>
		public PrimitiveType PrimitiveType {
			get { return _primitiveType; }
		}

		/// <summary>
		/// Gets the vertices of vertex buffer.
		/// </summary>
		/// <value>The vertices of vertex buffer.</value>
		public List<Vertex2D> Vertices {
			get { return _vertices; } 
		}

		/// <summary>
		/// Gets the size of the vertex structure.
		/// </summary>
		/// <value>The size of the vertex structure.</value>
		public int VertexSize {
			get { return Marshal.SizeOf(typeof(Vertex2D)); }
		}

		/// <summary>
		/// Gets the vertex count currently present in buffer.
		/// </summary>
		/// <value>The vertex count currently present in buffer.</value>
		public int VertexCount {
			get { return _vertexCount; }
		}

		/// <summary>
		/// Updates the content of vertex buffer from vertex list.
		/// </summary>
		public void Update() {
			DropBuffer();

			if (Vertices.Count > 0) {
				CreateBuffer(Vertices.Count);

				int bufferSize = VertexSize * VertexCount;
				unsafe {
					void* pData = null;
					HResult hr = _vertexBuffer.Lock((uint)0, (uint)bufferSize, out pData, D3DLockFlags.Disacrd);
					Watcher.Assert(hr, "Lock static vertex buffer");

					Vertex2D* vertices = (Vertex2D*)pData;
					for (int n=0; n < _vertexCount; ++n) {
						vertices[n] = _vertices[n];
					}

					_vertexBuffer.Unlock();
				}
			}			
		}

		/// <summary>
		/// Adds the object into vertex buffer.
		/// </summary>
		/// <typeparam name="O">Type of object</typeparam>
		/// <param name="obj">The object to add into buffer.</param>
		/// <param name="converter">The converter to use.</param>
		public void AddObject<O>(O obj, VertexConverter<O> converter) {
			if (null == converter) throw new ArgumentNullException("converter");

			Vertices.AddRange(converter(obj));
		}

		/// <summary>
		/// Adds the array of objects into vertex buffer.
		/// </summary>
		/// <typeparam name="O">Type of objectd</typeparam>
		/// <param name="objs">The objects to add into buffer.</param>
		/// <param name="converter">The converter to use.</param>
		public void AddObjects<O>(O[] objs, VertexConverter<O> converter) {
			if (null == objs) throw new ArgumentNullException("objs");
			if (null == converter) throw new ArgumentNullException("converter");

			for (int n = 0; n < objs.Length; ++n) {
				Vertices.AddRange(converter(objs[n]));
			}
		}

		/// <summary>
		/// Adds the array of objects into vertex buffer.
		/// </summary>
		/// <typeparam name="O">Type of objectd</typeparam>
		/// <param name="objs">The objects to add into buffer.</param>
		/// <param name="converter">The converter to use.</param>
		public void AddObjects<O>(IEnumerable<O> objs, VertexConverter<O> converter) {
			if (null == objs) throw new ArgumentNullException("objs");
			if (null == converter) throw new ArgumentNullException("converter");

			foreach (O o in objs) {
				Vertices.AddRange(converter(o));
			}
		}

	}
}
